// use bytes::{Buf, BufMut, Bytes, BytesMut};
use bytes::Bytes;

extern crate rcgen;
extern crate tokio;
use quinn::{default_runtime, Connection, EndpointConfig};
use quinn::{ClientConfig, Endpoint, ServerConfig};
use schmebulock::{start, GnomeId, Header, Message, Neighbor, Neighborhood, Payload, SwarmTime};
use std::error::Error;
use std::fmt;
use std::net::Ipv4Addr;
use std::net::UdpSocket;
use std::str::FromStr;
use std::sync::mpsc::{self, Receiver, Sender};
use std::time::Duration;
use std::{net::SocketAddr, sync::Arc};

#[derive(Debug)]
struct ConnectionError {
    num: u8,
}
impl fmt::Display for ConnectionError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "ConnectionError{}", self.num)
    }
}

impl Error for ConnectionError {
    // fn source(&self) -> Option<&(dyn Error + 'static)> {
    //     Some(&self.num)
    // }
}

#[tokio::main]
async fn main() -> Result<(), ConnectionError> {
    println!("Creating swarm manager");
    let mut manager = start();
    let mut neighbors: Vec<Neighbor> = Vec::with_capacity(3);
    let mut futu = None;
    println!("Creating local endpoint");
    let (connection_sender, connection_receiver) = mpsc::channel();
    make_local_connections(connection_sender.clone()).await;
    let mut num_id = 1;
    while let Ok(connection) = connection_receiver.try_recv() {
        println!(
            "Local connection created",
            // endpoint.local_addr(),
            // some_value
        );
        let (_local_sender, local_receiver) = mpsc::channel();
        let (remote_sender, remote_receiver) = mpsc::channel();
        let neighbor = Neighbor::from_id_channel_time(
            GnomeId(num_id),
            local_receiver,
            remote_sender,
            SwarmTime(0),
            SwarmTime(7),
        );
        num_id += 1;
        futu = Some(start_neighbor_thread(
            connection,
            _local_sender,
            remote_receiver,
        ));
        neighbors.push(neighbor);
    }
    manager.join_a_swarm("bezmiar".to_string(), Some(neighbors));

    if let Some(futu) = futu {
        futu.await;
    }
    // println!("After await");

    // let puncher = "tudbut.de:4277";
    // let swarm_name = "irdm".to_string();
    // let (remote_addr, socket, server) = holepunch(puncher, swarm_name);
    // if server {
    //     let _ = run_server(socket).await;
    //     // let _ = run_server(SocketAddr::from_str("0.0.0.0:0").unwrap());
    // } else {
    //     // let mut addr = udp_sock.local_addr().unwrap();
    //     // addr.set_port(0);
    //     // let _ = run_server(socket).await;
    //     // let client = make_client_endpoint(SocketAddr::from_str("0.0.0.0:0").unwrap(), &[]).unwrap();
    //     let (client, _cert) = make_endpoint(socket, server).unwrap();
    //     run_client(&client, remote_addr).await;
    // }
    Ok(())
}

async fn start_neighbor_thread(
    connection: Connection,
    sender: Sender<Message>,
    receiver: Receiver<Message>,
) {
    let mut i = 7;
    let _ = connection.send_datagram(Bytes::from_static(&[3, 2, 1]));
    while i > 0 {
        i -= 1;
        // println!("L ");
        match receiver.try_recv() {
            Ok(msg) => {
                // println!("Received from neighbor: {:?}", msg);
                let bytes = message_to_bytes(msg);
                let _ = connection.send_datagram(bytes);
            }
            Err(_e) => {
                // println!("Error receiving remote: {:?}", e);
            }
        }
        // println!("O ");
        // let _ = connection.send_datagram(Bytes::new());
        if let Ok(dgram) = connection.read_datagram().await {
            // println!("Received dgram");
            if dgram.is_empty() {
                let msg = bytes_to_message(dgram);
                let _ = sender.send(msg);
            } else {
                println!("Non empty drgam: {:?}", dgram);
                let msg = bytes_to_message(dgram);
                let _ = sender.send(msg);
            }
        };
        // println!("P ");
        // let _ = connection.send_datagram(Bytes::new());
    }
}

fn message_to_bytes(_msg: Message) -> Bytes {
    println!("{:?}", _msg);
    Bytes::from_static(&[4, 4, 3, 5, 5, 6])
}

fn bytes_to_message(_dgram: Bytes) -> Message {
    println!("DG: {:?}", _dgram);
    Message {
        swarm_time: SwarmTime(0),
        neighborhood: Neighborhood(0),
        header: Header::Sync,
        payload: Payload::KeepAlive,
    }
}

fn holepunch(puncher: &str, swarm_name: String) -> (SocketAddr, UdpSocket, bool) {
    println!("Establishing connection with helper...");
    let bind_addr = (Ipv4Addr::from(0_u32), 0);
    let holepunch = UdpSocket::bind(bind_addr).expect("unable to create socket");
    holepunch
        .connect(puncher)
        .expect("unable to connect to helper");
    let bytes = swarm_name.as_bytes();
    // let mut buf = BytesMut::zeroed(200);
    let mut buf = vec![0_u8; 200];
    // buf.put(swarm_name.as_bytes());
    buf[..bytes.len().min(200)].copy_from_slice(&bytes[..bytes.len().min(200)]);
    holepunch.send(&buf).expect("unable to talk to helper");
    holepunch
        .recv(&mut buf)
        .expect("unable to receive from helper");
    // buf should now contain our partner's address data.
    buf.retain(|e| *e != 0);
    let bind_addr = String::from_utf8_lossy(buf.as_slice()).to_string();
    // println!("to be bind addr: {}", bind_addr);
    println!(
        "Holepunching {} (partner) and :{} (you).",
        bind_addr,
        holepunch.local_addr().unwrap().port()
    );
    let s_addr = SocketAddr::from_str(&bind_addr);
    if s_addr.is_err() {
        println!("Coś poszło nie tak");
        ::std::process::exit(1);
    }
    let remote_adr = s_addr.unwrap();
    holepunch.connect(remote_adr).unwrap();
    // println!("SADR: {:?}", s_addr);
    holepunch
        .set_read_timeout(Some(Duration::from_secs(5)))
        .unwrap();
    holepunch
        .set_write_timeout(Some(Duration::from_secs(1)))
        .unwrap();
    println!("Holepunch and connection successful.");
    let mut my_external_addr = vec![0; 200];
    let _result = holepunch.recv(&mut my_external_addr);
    my_external_addr.retain(|e| *e != 0);
    if !my_external_addr.is_empty() {
        let my_addr = String::from_utf8_lossy(&my_external_addr).to_string();
        println!("Received external addr: {}", my_addr);
        let my_e_port = my_addr
            .split(':')
            .nth(1)
            .unwrap()
            .parse::<u16>()
            .expect("Unable to parse external port");
        let _ = holepunch.send(bind_addr.as_bytes());
        // println!("Sent: {:?}", bind_addr);
        if holepunch.local_addr().expect("Dunno my local addr").port() == my_e_port {
            println!("I can define my external port number!");
        }
        let _result = holepunch.recv(&mut my_external_addr);
        let should_be_server = my_e_port < remote_adr.port();
        (
            // holepunch.local_addr().unwrap(),
            remote_adr,
            holepunch,
            should_be_server,
        )
    } else {
        panic!("Did not receive data from remote host");
    }
}

/// Runs a QUIC server bound to given address and returns server certificate.
async fn run_server(socket: UdpSocket) -> Result<Vec<u8>, ConnectionError> {
    // async fn run_server(socket: UdpSocket) -> Result {
    println!("Starting QUIC server");
    use std::fs::File;
    use std::io::Write;
    let path = "results.txt";
    let mut output = File::create(path).expect("Unable to create results.txt file");
    // let (endpoint, server_cert) = make_server_endpoint(addr)?;
    let (endpoint, server_cert) = make_endpoint(socket, true).unwrap();
    println!("QUIC server endpoint created");
    // accept a single connection
    let _ = tokio::spawn(async move {
        let connection = endpoint.accept().await.unwrap().await.unwrap();
        println!(
            "[server] incoming connection: addr={}",
            connection.remote_address()
        );
        let to_send = Bytes::from(vec![9, 8, 7, 6, 5, 4, 3, 2, 1, 0]);
        if connection.send_datagram(to_send.clone()).is_ok() {
            let _ = writeln!(output, "Sent: {:?}", to_send);
        };
        let dgram_fut = connection.read_datagram();
        let bytes = dgram_fut.await;
        let line = format!("Recv: {:?}", bytes);
        let _ = writeln!(output, "{}", line);
    })
    .await;
    Ok(server_cert)
}

/// Attempt QUIC connection with the given server address.
async fn run_client(endpoint: &Endpoint, server_addr: SocketAddr) {
    println!("Starting QUIC client {:?}", server_addr,);
    let connect = endpoint
        .connect(server_addr, &server_addr.ip().to_string())
        .unwrap();
    let connection = connect.await.unwrap();
    println!("[client] connected: addr={}", connection.remote_address());
    let dgram_fut = connection.read_datagram();
    let bytes = dgram_fut.await;
    if let Ok(bytes) = bytes {
        println!("Recv: {:?}", bytes);
        let to_send = Bytes::from(vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
        println!("Sent: {:?}", to_send);
        let _ = connection.send_datagram(to_send);
        let _ = connection.read_datagram().await;
    } else {
        println!("Error receiving bytes.");
    }
    println!("Terminating QUIC client {:?}", server_addr,);
}

pub fn make_endpoint(
    socket: UdpSocket,
    server: bool,
    // server_certs: &[&[u8]],
) -> Result<(Endpoint, Vec<u8>), Box<dyn Error>> {
    println!("Making endpoint from socket: {:?}", &socket);
    let (server_config, server_cert) = configure_server()?;
    let client_cfg = configure_client();
    let runtime = default_runtime()
        .ok_or_else(|| std::io::Error::new(std::io::ErrorKind::Other, "no async runtime found"))?;
    let mut endpoint = Endpoint::new(
        EndpointConfig::default(),
        Some(server_config),
        socket,
        runtime,
    )
    .unwrap();
    if !server {
        endpoint.set_default_client_config(client_cfg);
    }
    Ok((endpoint, server_cert))
}

pub async fn make_local_connections(sender: Sender<Connection>) {
    let server_addr = "127.0.0.1:1026";
    let socket: UdpSocket = UdpSocket::bind("127.0.0.1:0").expect("Unable to bind socket");
    let (endpoint, _data) = make_endpoint(socket, false).unwrap();
    let connecting = endpoint
        .connect(SocketAddr::from_str(server_addr).unwrap(), "localhost")
        .unwrap()
        .await;
    if let Ok(conn) = connecting {
        let _ = sender.send(conn);
    } else {
        println!("Not connected locally, starting server");
        let bind_result = UdpSocket::bind(server_addr);
        if let Ok(socket) = bind_result {
            let (endpoint, _data) = make_endpoint(socket, true).unwrap();
            let incoming_conn = endpoint.accept().await.unwrap();
            if let Ok(conn) = incoming_conn.await {
                let _ = sender.send(conn);
            } else {
                println!("no local connections established");
            };
        } else {
            println!("Unable to bind socket");
        };
    }
    // socket
    //     .set_read_timeout(Some(Duration::new(5, 0)))
    //     .expect("Unable to set read timeout");
    // socket.set_broadcast(true).expect("Unable to set broadcast");
    // println!("Connected on port {}", port);
    // // println!("Broadcast: {:?}", socket.broadcast());
    // // println!("Timeout: {:?}", socket.read_timeout());
    // let local_addr = socket.local_addr().unwrap();
    // println!("Local addr: {:?}", local_addr);

    // let mut buf: Vec<u8> = Vec::with_capacity(200);
    // for byte in local_addr.to_string().into_bytes()
    // buf.append(&mut local_addr.to_string().into_bytes());
    // println!("Sending call, {:?} bytes ({})", buf, buf.len());
    // match socket.send_to(&buf, "255.255.255.255:1026") {
    //     Ok(n) => {
    //         if n != buf.len() {
    //             // return Err(Error::new(
    //             //     ErrorKind::Other,
    //             //     "Sent the wrong number of bytes",
    //             // ));
    //             println!("Did not match bytes sent.");
    //         } else {
    //             println!("sent required amount of bytes.");
    //             // Do nothing because we sent the number of bytes we expected to send
    //         }
    //     }
    //     Err(e) => println!("Error sending local data: {:?}", e),
    // }

    // println!("Awaiting responses..."); // self.recv_buff is a [u8; 8092]
    // while let Ok((n, addr)) = socket.recv_from(&mut buf) {
    //     println!("{} bytes response from {:?}: {:?}", n, addr, buf);
    //     if let Ok((endpoint, _data)) = make_endpoint(socket.try_clone().unwrap(), true) {
    //         let connection = endpoint
    //             .connect(addr, "local_server")
    //             .unwrap()
    //             .await
    //             .unwrap();
    //         println!("Local connection created");
    //         let _ = sender.send(connection);
    //     }
    // }
    // println!("No responses received");

    // let bind_result = UdpSocket::bind("0.0.0.0:1026");
    // if let Ok(socket) = bind_result {
    //     let _ = socket.set_read_timeout(Some(Duration::new(5, 0)));
    //     println!("Socket bind succesful");
    //     while let Ok((n, addr)) = socket.recv_from(&mut buf) {
    //         println!("{} bytes of data received from {:?}: {:?}", n, addr, buf);
    //         let _ = socket.send_to(&buf, addr);
    //         let (endpoint, _data) = make_endpoint(socket.try_clone().unwrap(), true).unwrap();
    //         println!("Endpoint created");
    //         let connection = endpoint.accept().await.unwrap().await.unwrap();
    //         println!("Connection established.");
    //         let _ = sender.send(connection);
    //         println!("Connection sent.");
    //     }
    // } else {
    //     println!("Unable to bind socket");
    // }
}

/// Returns default server configuration along with its certificate.
fn configure_server() -> Result<(ServerConfig, Vec<u8>), ConnectionError> {
    let cert = rcgen::generate_simple_self_signed(vec!["localhost".into()]).unwrap();
    let cert_der = cert.serialize_der().unwrap();
    let priv_key = cert.serialize_private_key_der();
    let priv_key = rustls::PrivateKey(priv_key);
    let cert_chain = vec![rustls::Certificate(cert_der.clone())];

    let mut server_config =
        ServerConfig::with_single_cert(cert_chain, priv_key).expect("Unable to build ServerConfig");
    let transport_config = Arc::get_mut(&mut server_config.transport).unwrap();
    transport_config.max_concurrent_uni_streams(0_u8.into());

    Ok((server_config, cert_der))
}

#[allow(unused)]
pub const ALPN_QUIC_HTTP: &[&[u8]] = &[b"hq-29"];

// Dummy certificate verifier that treats any certificate as valid.
/// NOTE, such verification is vulnerable to MITM attacks, but convenient for testing.
struct SkipServerVerification;

impl SkipServerVerification {
    fn new() -> Arc<Self> {
        Arc::new(Self)
    }
}

impl rustls::client::ServerCertVerifier for SkipServerVerification {
    fn verify_server_cert(
        &self,
        _end_entity: &rustls::Certificate,
        _intermediates: &[rustls::Certificate],
        _server_name: &rustls::ServerName,
        _scts: &mut dyn Iterator<Item = &[u8]>,
        _ocsp_response: &[u8],
        _now: std::time::SystemTime,
    ) -> Result<rustls::client::ServerCertVerified, rustls::Error> {
        Ok(rustls::client::ServerCertVerified::assertion())
    }
}

fn configure_client() -> ClientConfig {
    let crypto = rustls::ClientConfig::builder()
        .with_safe_defaults()
        .with_custom_certificate_verifier(SkipServerVerification::new())
        .with_no_client_auth();

    ClientConfig::new(Arc::new(crypto))
}
